package com.ansion.utils.image;

import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.drawable.Drawable;
import android.media.ExifInterface;
import android.media.MediaMetadataRetriever;
import android.media.MediaScannerConnection;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.provider.MediaStore;
import android.text.TextUtils;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.ansion.log.VLog;
import com.ansion.utils.CloseUtils;
import com.ansion.utils.FileUtil;
import com.ansion.utils.ResourceUtils;
import com.ansion.utils.UiUtils;
import com.ansion.utils.ValueSafeTransfer;
import com.ansion.utils.file.FileMimeTypes;
import com.ansion.utils.file.FileUtils;
import com.ansion.utils.file.SFileProvider;

import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLConnection;

import okio.ByteString;

public class ImageUtils {

    public static final String EVENT_PHOTO_CHANGE = "app.media.photo.change";
    public static final String EVENT_VIDEO_CHANGE = "app.media.video.change";
    public static final String BOTIM = "BOTIM";
    public static final String MOVIES_BOTIM = "Movies/BOTIM";
    public static final String PICTURES_BOTIM = "Pictures/BOTIM";

    @Nullable
    public static String parseImageMimeType(@NonNull File file) {
        if (!file.exists() || !file.isFile()) {
            return null;
        }
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(file.getAbsolutePath(), options);
        return options.outMimeType;
    }

    public static CompressFormat selectImageFormat(String mimeType) {
        if (TextUtils.equals(mimeType, FileMimeTypes.MIME_IMAGE_PNG)) {
            return CompressFormat.PNG;
        } else {
            return CompressFormat.JPEG;
        }
    }

    public static boolean isImageFile(@NonNull File file) {
        return parseImageMimeType(file) != null;
    }

    @Nullable
    public static byte[] compressToBytes(@NonNull Bitmap bitmap, @NonNull CompressFormat format, int quality) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream(65536);
        if (!bitmap.compress(format, quality, baos)) {
            return null;
        }
        return baos.toByteArray();
    }

    public static String getBitmapMimeType(byte[] data) {
        if (data == null)
            return null;
        ByteArrayInputStream bis = new ByteArrayInputStream(data);
        final BitmapFactory.Options options = new BitmapFactory.Options();
        try {
            options.inJustDecodeBounds = true;
            BitmapFactory.decodeStream(bis, null, options);
        } catch (Exception e) {
            return null;
        } finally {
            CloseUtils.silentlyClose(bis);
        }
        return options.outMimeType;
    }

    public static boolean compressToFile(@NonNull Bitmap bitmap, @NonNull File file,
                                         CompressFormat format, int quality) {
        try (FileOutputStream fos = new FileOutputStream(file)) {
            return bitmap.compress(format, quality, fos);
        } catch (Exception e) {
            VLog.w("TAG_BASE_MISC", " failed to compress bitmap to file: %s", file.getAbsoluteFile());
            return false;
        }
    }

    public static Bitmap drawableToBitmap(Drawable drawable) {
        if (drawable == null) {
            return null;
        }
        return drawableToBitmap(drawable, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
    }

    public static Bitmap drawableToBitmap(Drawable drawable, int width, int height) {
        if (drawable == null) {
            return null;
        }
        Bitmap bitmap = Bitmap.createBitmap(width, height, drawable.getOpacity() != PixelFormat.OPAQUE ? Config.ARGB_8888 : Config.RGB_565);

        Canvas canvas = new Canvas(bitmap);
        drawable.setBounds(0, 0, width, height);
        drawable.draw(canvas);

        return bitmap;
    }


    /**
     * 保存bitmap到本地
     */
    public static File saveBitmap(Bitmap bitmap, File file) {
        if (bitmap == null) {
            return file;
        }
        try {
            BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file));
            bitmap.compress(CompressFormat.JPEG, 100, bos);
            bos.flush();
            bos.close();
            notifyPhotos(ResourceUtils.getContext(), bitmap, file.getAbsolutePath());
            return file;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 保存bitmap到本地
     */
    public static File copyBitmapOnly(Bitmap bitmap, File file) {
        if (bitmap == null) {
            return file;
        }
        try {
            BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file));
            bitmap.compress(CompressFormat.JPEG, 100, bos);
            bos.flush();
            bos.close();
            return file;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }


    /**
     * 要求的是绝对路径，并且在 SD 卡上
     *
     * @param context 上下文
     *                //     * @param filepath 文件路径
     * @return file path.
     */
    public static String notifyGallery(Context context, File file, boolean isVideo) {
        String filepath = file.getPath();
        return notifyGallery(context, filepath, isVideo);
    }

    public static String notifyGallery(Context context, String filepath, boolean isVideo) {
        String savedFilePath = null;
        // Add a specific media item.
        ContentResolver resolver = context
                .getContentResolver();
        Uri videoCollection = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q ? MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL) : MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
        Uri imageCollection = isVideo ? videoCollection : MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
        String displayNameKey = isVideo ? MediaStore.Video.Media.DISPLAY_NAME : MediaStore.Images.Media.DISPLAY_NAME;
        String titleKey = isVideo ? MediaStore.Video.Media.TITLE : MediaStore.Images.Media.TITLE;
        String mimeKey = isVideo ? MediaStore.Video.Media.MIME_TYPE : MediaStore.Images.Media.MIME_TYPE;
        long mImageTime = System.currentTimeMillis();
// Publish a new picture.
        String filename = FileUtils.getFileName(filepath);
        String mimeType = URLConnection.guessContentTypeFromName(filename);
        ContentValues newImageDetails = new ContentValues();
        newImageDetails.put(displayNameKey,
                filename);
        newImageDetails.put(titleKey, filename);
        newImageDetails.put(mimeKey, mimeType);

        newImageDetails.put(MediaStore.MediaColumns.DATE_ADDED, mImageTime / 1000);
        newImageDetails.put(MediaStore.MediaColumns.DATE_MODIFIED, mImageTime / 1000);
//        newImageDetails.put(MediaStore.MediaColumns.DATE_EXPIRES, (mImageTime + DateUtils.DAY_IN_MILLIS) / 1000);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            newImageDetails.put(MediaStore.MediaColumns.IS_PENDING, 1);
            newImageDetails.put(MediaStore.MediaColumns.RELATIVE_PATH, isVideo ? MOVIES_BOTIM : PICTURES_BOTIM);
        } else {
            newImageDetails.put(isVideo ? MediaStore.Video.Media.DATA : MediaStore.Images.Media.DATA, filepath);
        }
//                newImageDetails.put("duration", duration);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            Uri imageUri = resolver.insert(imageCollection, newImageDetails);
            if (imageUri != null) {
                try (OutputStream out = resolver.openOutputStream(imageUri)) {
                    FileUtils.copyFile(filepath, out);
                    // Everything went well above, publish it!
                    newImageDetails.clear();
                    newImageDetails.put(MediaStore.MediaColumns.IS_PENDING, 0);
                    newImageDetails.putNull(MediaStore.MediaColumns.DATE_EXPIRES);
                    resolver.update(imageUri, newImageDetails, null, null);
                    savedFilePath = FileUtil.getRealPathFromURI(ResourceUtils.getContext(), imageUri);
                } catch (IOException e) {
                    VLog.e("TAG_BASE_MISC", "notify image to gallery fail,path:" + filepath + ",error:" + e.getMessage());
                }
            }
        } else {
            File files = new File(filepath);
            if (files.exists()) {
                File baseDir;
                if (isVideo) {
                    baseDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES);
                } else {
                    baseDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
                }
                savedFilePath = saveToAlbumBeforeQ(context, baseDir, filepath, mimeType);
            }
        }
        return TextUtils.isEmpty(savedFilePath) ? "" : savedFilePath;
    }

    private static String saveToAlbumBeforeQ(Context context, File baseDir, String filepath, String mimeType) {
        File file = new File(filepath);
        baseDir.mkdir();
        File botDir = new File(baseDir, BOTIM);
        botDir.mkdir();
        String name = System.currentTimeMillis() + "_" + file.getName();
        File destFile = new File(botDir, name);
        try {
            destFile.createNewFile();
        } catch (IOException e) {
            e.printStackTrace();
        }
        File sourceFile = new File(filepath);
        if (sourceFile.exists()) {
            try {
                FileUtils.copyFile(sourceFile, destFile);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        MediaScannerConnection.scanFile(
                context,
                new String[]{destFile.getAbsolutePath()},
                new String[]{mimeType},
                (path, uri) -> {
                    // Scan Completed
                });
        return destFile.getAbsolutePath();
    }

    private static void notifyPhotos(Context context, Bitmap bitmap, String filePath) {
        putBitmapToMedia(context, filePath, bitmap);
        notifyGallery(context, filePath, false);
        bitmap.recycle();
    }

    private static void putBitmapToMedia(Context context, String filePath, Bitmap bm) {
        ContentValues values = new ContentValues();
        values.put(MediaStore.Images.Media.DISPLAY_NAME, filePath);
        values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
        Uri uri = context.getApplicationContext().getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
        try {
            OutputStream out = context.getContentResolver().openOutputStream(uri);
            bm.compress(CompressFormat.JPEG, 100, out);
            out.flush();
            out.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    public static String getPreviewBase64(String localPath) {
        try {
            Bitmap blurBitmap = ImageUtils.getCompressBitmap(ResourceUtils.getContext(), SFileProvider.getUriForFile(new File(localPath)), 1024, true);
            if (null != blurBitmap) {
                return ByteString.of(Bitmap2JPEGBytes(blurBitmap)).base64();
            } else {
                return null;
            }
        } catch (Throwable e) {
            e.printStackTrace();
            VLog.e("ChatTAG_CHAT_IMAGE", e);
        }
        return null;
    }

    public static byte[] Bitmap2JPEGBytes(Bitmap bitmap) {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        bitmap.compress(CompressFormat.JPEG, 100, outputStream);
        return outputStream.toByteArray();
    }

    public static Bitmap getCompressBitmap(Context context, Uri uri, long maxSize) {
        return getCompressBitmap(context, uri, maxSize, Config.ARGB_8888);
    }

    public static Bitmap getCompressBitmap(Context context, Uri uri, long maxSize, boolean needWhiteBack) {
        return _getCompressBitmap(context, uri, maxSize, Config.ARGB_8888, needWhiteBack);
    }

    public static Bitmap getCompressBitmap(Context context, Uri uri, long maxSize, Config config) {
        return _getCompressBitmap(context, uri, maxSize, config, false);
    }

    private static Bitmap _getCompressBitmap(Context context, Uri uri, long maxSize, Config config, boolean needWhiteBack) {
        int originalWidth;
        int originalHeight;
        try (InputStream input = context.getContentResolver().openInputStream(uri)) {
            BitmapFactory.Options onlyBoundsOptions = new BitmapFactory.Options();
            onlyBoundsOptions.inJustDecodeBounds = true;
            onlyBoundsOptions.inDither = true;//optional
            onlyBoundsOptions.inPreferredConfig = config;//optional
            BitmapFactory.decodeStream(input, null, onlyBoundsOptions);
            if ((onlyBoundsOptions.outWidth == -1) || (onlyBoundsOptions.outHeight == -1)) {
                return null;
            }
            originalWidth = onlyBoundsOptions.outWidth;
            originalHeight = onlyBoundsOptions.outHeight;
        } catch (IOException e) {
            VLog.eonly(e);
            return null;
        }


        try (InputStream input = context.getContentResolver().openInputStream(uri)) {
            int inSampleSize = calculateSampleSize(originalWidth, originalHeight, maxSize, config);
            //4.start compress
            BitmapFactory.Options bitmapOptions = new BitmapFactory.Options();
            bitmapOptions.inSampleSize = inSampleSize;
            bitmapOptions.inDither = true;//optional
            bitmapOptions.inPreferredConfig = config;//optional
            Bitmap decodeResult = BitmapFactory.decodeStream(input, null, bitmapOptions);
            if (needWhiteBack) {
                return bitmapAddWhiteBack(decodeResult);
            } else {
                return decodeResult;
            }
        } catch (IOException e) {
            VLog.eonly(e);
            return null;
        }
    }

    private static Bitmap bitmapAddWhiteBack(Bitmap bitmap) {
        Bitmap convertResult = bitmap.copy(Config.ARGB_8888, true);
        Canvas canvas = new Canvas(convertResult);
        canvas.drawColor(Color.WHITE);
        canvas.drawBitmap(bitmap, 0, 0, null);
        return convertResult;
    }

    public static byte[] bitmap2Bytes(@NonNull Bitmap bmp) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        bmp.compress(CompressFormat.JPEG, 100, baos);
        byte[] data = baos.toByteArray();
        return data;
    }

    public static Bitmap getBitmapCopy(Context context, @NonNull Bitmap bitmap) {
        byte[] bytes = bitmap2Bytes(bitmap);
        return BitmapFactory.decodeByteArray(bytes, 0, bytes.length, null);
    }

    public static Bitmap getCompressBitmap(Context context, @NonNull Bitmap bitmap, long maxSize, Config config) {
        int originalWidth;
        int originalHeight;
        byte[] bytes = bitmap2Bytes(bitmap);
        try {

            BitmapFactory.Options onlyBoundsOptions = new BitmapFactory.Options();
            onlyBoundsOptions.inJustDecodeBounds = true;
            onlyBoundsOptions.inDither = true;//optional
            onlyBoundsOptions.inPreferredConfig = config;//optional
            BitmapFactory.decodeByteArray(bytes, 0, bytes.length, onlyBoundsOptions);
            if ((onlyBoundsOptions.outWidth == -1) || (onlyBoundsOptions.outHeight == -1)) {
                return null;
            }
            originalWidth = onlyBoundsOptions.outWidth;
            originalHeight = onlyBoundsOptions.outHeight;
        } catch (Exception e) {
            VLog.eonly(e);
            return null;
        }


        try {
            int inSampleSize = calculateSampleSize(originalWidth, originalHeight, maxSize, config);
            //4.start compress
            BitmapFactory.Options bitmapOptions = new BitmapFactory.Options();
            bitmapOptions.inSampleSize = inSampleSize;
            bitmapOptions.inDither = true;//optional
            bitmapOptions.inPreferredConfig = config;//optional
            return BitmapFactory.decodeByteArray(bytes, 0, bytes.length, bitmapOptions);
        } catch (Exception e) {
            VLog.eonly(e);
            return null;
        }
    }

    public static int calculateSampleSize(int width, int height, double size, Config config) {
        int[] widthAndHeight = getCompressedWidthAndHeight(width, height, size, config);
        int widthOfCompressed = widthAndHeight[0];
        //3.calculate compress sample size
        float scalePercent = (float) width / widthOfCompressed;
        int inSampleSize;
        if (scalePercent % 1 > 0) {
            inSampleSize = (int) scalePercent + 1;
        } else {
            inSampleSize = (int) scalePercent;
        }
        if (inSampleSize <= 0) {
            inSampleSize = 1;
        }
        return inSampleSize;
    }

    /**
     * calculate width and height for a bitmap witch will be compressed
     *
     * @param width  origin bitmap width
     * @param height origin bitmap height
     * @param size   max size
     * @param config compress formation
     */
    public static int[] getCompressedWidthAndHeight(int width, int height, double size, Config config) {
        int[] widthAndHeight = {width, height};
        //1.proportion of original bitmap
        float ratioOfBitmap = (float) width / height;

        int bytesOfOnePixel = 1;
        switch (config) {
            case ARGB_8888:
                bytesOfOnePixel = 4;
                break;
            case ALPHA_8:
                bytesOfOnePixel = 1;
                break;
            case ARGB_4444:
            case RGB_565:
                bytesOfOnePixel = 2;
                break;
        }

        //2.calculate pixels count of compressed bitmap
        long pixels = (long) (size / bytesOfOnePixel);
        int expectWidth = (int) Math.sqrt(pixels * ratioOfBitmap);
        if (expectWidth < width) {
            widthAndHeight[0] = expectWidth;
            widthAndHeight[1] = (int) (widthAndHeight[0] / ratioOfBitmap);
        }
//        S.s("=======>>>> need[" + size + "]   before compress[" + (width * height * bytesOfOnePixel) + "]:w:" + width + " h:" + height + " after compress[" + (widthAndHeight[0] * widthAndHeight[1] * bytesOfOnePixel) + "]:w" + widthAndHeight[0] + " h:" + widthAndHeight[1]);
        return widthAndHeight;
    }

    private static String insertImageToSystem(Context context, String imagePath, String name) {
        String url = "";
        try {
            url = MediaStore.Images.Media.insertImage(context.getContentResolver(), imagePath, name, null);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        return url;
    }

    /**
     * @return Return [width, height]
     */
    public static int[] adjustImageSize(final int imageWidth, final int imageHeight, boolean isGif) {
        int widthScreen = UiUtils.getScreenWidthPixels();

        int viewWidth;
        int viewHeight;

        float minRatio = 0.713f;
        float maxRatio = 4;
        float imageRatio = (imageWidth == 0 || imageHeight == 0) ? 0.7f : (float) imageWidth / imageHeight;

        boolean isHorizontalImage = imageRatio >= 1;

        if (isHorizontalImage || isGif) {
            viewWidth = (int) (widthScreen * 0.7f);
        } else {
            viewWidth = (int) (widthScreen * 0.64f);
        }
        if (imageRatio > maxRatio) {
            viewHeight = (int) (viewWidth / maxRatio);
        } else if (imageRatio < minRatio) {
            viewHeight = (int) (viewWidth / minRatio);
        } else {
            viewHeight = (int) (viewWidth / imageRatio);
        }
        return new int[]{viewWidth, viewHeight};
    }

    public static int getImageOrientation(@NonNull String filePath) {
        try {
            return new ExifInterface(filePath).getAttributeInt(ExifInterface.TAG_ORIENTATION,
                    ExifInterface.ORIENTATION_UNDEFINED);
        } catch (Throwable e) {
            VLog.e(e, "TAG_BASE_MISC", "failed to read EXIF info: %s", filePath);
        }
        return ExifInterface.ORIENTATION_UNDEFINED;
    }

    /**
     * Calculate the largest inSampleSize value that is a power of 2 and keeps both
     * height and width larger than the requested height and width.
     */
    public static int calculateInSampleSize(int width, int height, int reqWidth, int reqHeight) {
        int inSampleSize = 1;

        if (height > reqHeight || width > reqWidth) {
            final int halfHeight = height / 2;
            final int halfWidth = width / 2;

            while ((halfHeight / inSampleSize) >= reqHeight
                    && (halfWidth / inSampleSize) >= reqWidth) {
                inSampleSize *= 2;
            }
        }

        return inSampleSize;
    }


    /**
     * Decode and sample down a bitmap from image bytes to the requested width and height.
     *
     * @param bytes     The image bytes to decode
     * @param reqWidth  The requested width of the resulting bitmap
     * @param reqHeight The requested height of the resulting bitmap
     * @return A bitmap sampled down from the original with the same aspect ratio and dimensions
     * that are equal to or greater than the requested width and height
     */
    public static Bitmap decodeSampledBitmap(@NonNull byte[] bytes, int reqWidth, int reqHeight,
                                             @NonNull String logKey) {
        // First decode with inJustDecodeBounds=true to check dimensions
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);

        // Calculate inSampleSize
        options.inSampleSize = calculateInSampleSize(options.outWidth, options.outHeight, reqWidth, reqHeight);
        VLog.d("TAG_BASE_MISC", "decode sampled bitmap[%s], origin size: [%d, %d], sampleSize: %d",
                logKey, options.outWidth, options.outHeight, options.inSampleSize);

        // Decode bitmap with inSampleSize set
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);
    }

    public static Bitmap decodeSampledBitmap(File f, int maxWidth, int maxHeight) {
        if (f == null || !f.exists())
            return null;
        try {
            BitmapFactory.Options o = new BitmapFactory.Options();
            o.inJustDecodeBounds = true;
            BitmapFactory.decodeFile(f.getAbsolutePath(), o);

            int width = o.outWidth, height = o.outHeight;

            int scale = (int) Math.ceil(getScale(width, height, maxWidth,
                    maxHeight));
            BitmapFactory.Options o2 = new BitmapFactory.Options();
            o2.inSampleSize = scale;
            Bitmap bmp = BitmapFactory.decodeFile(f.getAbsolutePath(), o2);
            if (bmp == null)
                return null;
            return bmp;
        } catch (Exception e) {
            // LogUtil.error(TAG, e);
            return null;
        } catch (OutOfMemoryError e) {
            return null;
        }
    }

    private static final int MIN_PIC_WIDTH_OR_HEIGHT = 60;

    private static double getScale(int width, int height, int maxWidth,
                                   int maxHeight) {
        double scale = 1;
        if (width <= MIN_PIC_WIDTH_OR_HEIGHT
                && height <= MIN_PIC_WIDTH_OR_HEIGHT)
            return 1;

        if (width > maxWidth || height > maxHeight) {
            double scaleWidth = ((double) width) / maxWidth;
            double scaleHeight = ((double) height) / maxHeight;
            scale = scaleWidth > scaleHeight ? scaleWidth : scaleHeight;
            if (scale < 1)
                return 1;

            int newWidth = (int) (width / scale);
            if (newWidth < MIN_PIC_WIDTH_OR_HEIGHT) {
                return ((double) width) / MIN_PIC_WIDTH_OR_HEIGHT;
            }

            int newHeight = (int) (height / scale);
            if (newHeight < MIN_PIC_WIDTH_OR_HEIGHT) {
                return ((double) height)
                        / MIN_PIC_WIDTH_OR_HEIGHT;
            }
        }

        return scale;
    }

    /**
     * @param bitmap     原图
     * @param edgeLength 希望得到的正方形部分的边长
     * @return 缩放截取正中部分后的位图。
     */
    public static Bitmap centerSquareScaleBitmap(Bitmap bitmap, int edgeLength) {
        if (null == bitmap || edgeLength <= 0) {
            return null;
        }

        Bitmap result = bitmap;
        int widthOrg = bitmap.getWidth();
        int heightOrg = bitmap.getHeight();

        if (widthOrg > edgeLength && heightOrg > edgeLength) {
            //压缩到一个最小长度是edgeLength的bitmap
            int longerEdge = (int) (edgeLength * Math.max(widthOrg, heightOrg) / Math.min(widthOrg, heightOrg));
            int scaledWidth = widthOrg > heightOrg ? longerEdge : edgeLength;
            int scaledHeight = widthOrg > heightOrg ? edgeLength : longerEdge;
            Bitmap scaledBitmap;

            try {
                scaledBitmap = Bitmap.createScaledBitmap(bitmap, scaledWidth, scaledHeight, true);
            } catch (Exception e) {
                return null;
            }

            //从图中截取正中间的正方形部分。
            int xTopLeft = (scaledWidth - edgeLength) / 2;
            int yTopLeft = (scaledHeight - edgeLength) / 2;

            try {
                result = Bitmap.createBitmap(scaledBitmap, xTopLeft, yTopLeft, edgeLength, edgeLength);
                scaledBitmap.recycle();
            } catch (Exception e) {
                return null;
            }
        }

        return result;
    }


    /**
     * @param bitmap     原图
     * @param edgeHeight 希望得到的正方形尺寸
     * @param edgeWith   从最中心的图中截取的宽度
     * @return 缩放截取正中部分后的位图。比如图片宽高是100*200，需要截取宽度为50的图片最中心部分，则先生成100*100的图片，
     * 再从100*100图片中间抽取50*100的图片
     */
    public static Bitmap centerSquareScaleBitmap(Bitmap bitmap, int edgeHeight, int edgeWith) {
        Bitmap centerBitmap = centerSquareScaleBitmap(bitmap, edgeHeight);
        Bitmap result = centerBitmap;
        if (edgeWith < edgeHeight) {
            int xTopLeft = (edgeHeight - edgeWith) / 2;
//            int yTopLeft = 0;
            try {
                result = Bitmap.createBitmap(centerBitmap, xTopLeft, 0, edgeWith, edgeHeight);
                centerBitmap.recycle();
            } catch (Exception e) {
                return null;
            }
        }

        return result;
    }

    public static boolean isImageFile(String path) {
        String mimeType = URLConnection.guessContentTypeFromName(path);
        if (TextUtils.isEmpty(mimeType)) {
            mimeType = parseImageMimeType(new File(path));
        }
        return mimeType != null && mimeType.startsWith("image");
    }

    public static boolean isVideoFile(String path) {
        String mimeType = URLConnection.guessContentTypeFromName(path);
        if (TextUtils.isEmpty(mimeType)) {
            mimeType = parseImageMimeType(new File(path));
        }
        return mimeType != null && mimeType.startsWith("video");
    }

    public static int[] getImageSize(String path) {
        try {
            int rotation = getImageRotate(path);
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inJustDecodeBounds = true;
            BitmapFactory.decodeFile(path, options);
            int imageWidth = options.outWidth;
            int imageHeight = options.outHeight;
            if (rotation % 180 > 0) {
                int tmp = imageHeight;
                imageHeight = imageWidth;
                imageWidth = tmp;
            }
            return new int[]{imageWidth, imageHeight, rotation};
        } catch (Throwable ignore) {
            return new int[]{0, 0, 0};
        }
    }

    public static int getImageRotate(String localPath) {
        int orientation = 0;
        try {
            ExifInterface exif = new ExifInterface(localPath);
            int attribute = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 0);
            switch (attribute) {
                case ExifInterface.ORIENTATION_ROTATE_90:
                    orientation = 90;
                    break;
                case ExifInterface.ORIENTATION_ROTATE_180:
                    orientation = 180;
                    break;
                case ExifInterface.ORIENTATION_ROTATE_270:
                    orientation = 270;
                    break;
            }
        } catch (Throwable ignore) {
        }
        return orientation;
    }

    public static int[] getVideoSize(String localPath) {
        if (TextUtils.isEmpty(localPath) || !new File(localPath).exists()) {
            return new int[]{0, 0, 0, 0};
        }
        MediaMetadataRetriever retriever = null;
        try {
            retriever = new MediaMetadataRetriever();
            retriever.setDataSource(localPath);
            int width = ValueSafeTransfer.intValue(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH));
            int height = ValueSafeTransfer.intValue(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT));
            int rotation = ValueSafeTransfer.intValue(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION));
            int duration = ValueSafeTransfer.intValue(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION));
            if (rotation % 180 > 0) {
                int tmp = height;
                height = width;
                width = tmp;
            }
            return new int[]{width, height, rotation, duration};
        } catch (Throwable e) {
            e.printStackTrace();
            return new int[]{0, 0, 0, 0};
        } finally {
            if (retriever != null) {
                try {
                    retriever.release();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static Bitmap rotateBitmap(Bitmap bitmap, int orientation, boolean recycle) {
        Matrix matrix = new Matrix();
        switch (orientation) {
            case ExifInterface.ORIENTATION_ROTATE_90:
                matrix.setRotate(90);
                break;
            case ExifInterface.ORIENTATION_ROTATE_180:
                matrix.setRotate(180);
                break;
            case ExifInterface.ORIENTATION_ROTATE_270:
                matrix.setRotate(-90);
                break;
            case ExifInterface.ORIENTATION_FLIP_HORIZONTAL:
                matrix.setScale(-1, 1);
                break;
            case ExifInterface.ORIENTATION_FLIP_VERTICAL:
                matrix.setRotate(180);
                matrix.postScale(-1, 1);
                break;
            case ExifInterface.ORIENTATION_TRANSPOSE:
                matrix.setRotate(90);
                matrix.postScale(-1, 1);
                break;
            case ExifInterface.ORIENTATION_TRANSVERSE:
                matrix.setRotate(-90);
                matrix.postScale(-1, 1);
                break;
            case ExifInterface.ORIENTATION_UNDEFINED:
            case ExifInterface.ORIENTATION_NORMAL:
            default:
                return bitmap;
        }

        Bitmap rotatedBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
        if (recycle) {
            bitmap.recycle();
        }
        return rotatedBitmap;
    }

    public static void writeBitmap(String pathName, Bitmap bitmap,
                                   int compressRate) {
        if (null == bitmap || null == pathName)
            return;
        boolean bPng = false;
        if (pathName.endsWith(".png")) {
            bPng = true;
        }

        File _file = new File(pathName);
        boolean bNew = true;
        if (_file.exists()) {
            bNew = false;
            _file = new File(pathName + ".tmp");
            _file.delete();
        }
        FileOutputStream fos = null;
        boolean bOK = false;
        try {
            fos = new FileOutputStream(_file);
            if (bPng) {
                bitmap.compress(CompressFormat.PNG, compressRate, fos);
            } else {
                bitmap.compress(CompressFormat.JPEG, compressRate, fos);
            }
            bOK = true;
        } catch (Exception e) {
            VLog.eonly(e);
        } catch (Throwable t) {
            VLog.eonly(t);
        } finally {
            if (fos != null) {
                try {
                    fos.close();
                    if (bNew == false && bOK) {
                        _file.renameTo(new File(pathName));
                    }
                } catch (IOException e) {
                }
            }
        }
//		if (bOK && SwitchController.get().supportMediaSaveToNewFolder()) {
//			FileUtil.addMediaToGallery(pathName);
//		}
    }

    public static boolean isGif(String path) {
        File file = new File(path);
        if (!file.exists() || file.isDirectory()) {
            return false;
        }
        final Uri uri = SFileProvider.getUriForFile(file);
        return isGif(uri);
    }

    /***
     * 简单按照文件后缀名判断是否gif，避免在列表页快速滑动出现卡帧
     * @param path
     * @return
     */
    public static boolean isGifSimple(String path) {
        boolean isGif = path == null ? false : path.endsWith(".gif");
        return isGif;
    }

    public static boolean isGif(Uri uri) {
        if (uri == null) {
            return false;
        }
        InputStream inputStream = null;
        try {
            inputStream = ResourceUtils.getContentResolver().openInputStream(uri);
            byte[] header = new byte[3];
            if (inputStream.read(header, 0, 3) == 3) {
                String str = new String(header);
                if (str.equalsIgnoreCase("gif")) {
                    return true;
                }
            }
        } catch (Exception e) {
            VLog.e("ChatTAG_CHAT_IMAGE", e);
        } finally {
            try {
                if (inputStream != null) {
                    inputStream.close();
                }
            } catch (Exception e2) {
                VLog.e("ChatTAG_CHAT_IMAGE", e2);
            }
        }
        return false;
    }

    @Nullable
    public static Bitmap convertBase64ToBitmap(String previewBase64) {
        if (TextUtils.isEmpty(previewBase64)) {
            return null;
        }
        try {
            ByteString byteString = ValueSafeTransfer.base64DecodeByteStringValue(previewBase64);
            if (byteString != null) {
                byte[] blurData = byteString.toByteArray();
                if (blurData != null) {
                    return BitmapFactory.decodeByteArray(blurData, 0, blurData.length);
                }
            }
        } catch (Throwable ignore) {
        }
        return null;
    }


    public static Bitmap createCircleImage(Bitmap source) {
        int length = Math.min(source.getWidth(), source.getHeight());
        Paint paint = new Paint();
        paint.setAntiAlias(true);
        Bitmap target = Bitmap.createBitmap(length, length, Config.ARGB_8888);
        Canvas canvas = new Canvas(target);
        canvas.drawCircle(length * 0.5f, length * 0.5f, length * 0.5f, paint);
        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
        canvas.drawBitmap(source, 0, 0, paint);
        return target;
    }


    /***
     *
     * 图片高宽比在0.95和1.05之间不做截取
     * @param bitmap
     * @return
     */
    public static boolean needCrop(Bitmap bitmap) {
        if (bitmap == null || bitmap.isRecycled())
            return false;
        return Math.abs(bitmap.getHeight() - bitmap.getWidth()) > (int) (Math.min(bitmap.getWidth(), bitmap.getHeight()) * 5.0f / 100);
    }

    /***
     * 获取中间部分图片
     * @param bitmap
     * @return
     */
    public static Bitmap getCenterBitmap(Bitmap bitmap) {
        Bitmap centerBitmap = null;
        int width = bitmap.getWidth();
        int height = bitmap.getHeight();
        if (width >= height) {
            int xTopLeft = (int) ((width - height) / 2.0f);
            centerBitmap = Bitmap.createBitmap(bitmap, xTopLeft, 0, height, height);
        } else {
            int yTopLeft = (int) ((height - width) / 2.0f);
            centerBitmap = Bitmap.createBitmap(bitmap, 0, yTopLeft, width, width);
        }
        return centerBitmap;
    }


    public final static Bitmap getScaleImage(File f, int maxWidth, int maxHeight) {
        if (f == null || !f.exists())
            return null;
        try {
            BitmapFactory.Options o = new BitmapFactory.Options();
            o.inJustDecodeBounds = true;
            BitmapFactory.decodeFile(f.getAbsolutePath(), o);

            int width = o.outWidth, height = o.outHeight;

            int scale = (int) Math.ceil(getScale(width, height, maxWidth,
                    maxHeight));
            BitmapFactory.Options o2 = new BitmapFactory.Options();
            o2.inSampleSize = scale;
            Bitmap bmp = BitmapFactory.decodeFile(f.getAbsolutePath(), o2);
            if (bmp == null)
                return null;
            return bmp;
        } catch (Exception e) {
            // LogUtil.error(TAG, e);
            return null;
        } catch (OutOfMemoryError e) {
            return null;
        }
    }

    public static Bitmap addRoundedCorners(Bitmap originalBitmap, float radius) {
        if (originalBitmap == null || radius <= 0) {
            return null;
        }
        try {
            // 创建BitmapShader
            BitmapShader shader = new BitmapShader(originalBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);

            // 创建一个新的Bitmap
            Bitmap roundedBitmap = Bitmap.createBitmap(originalBitmap.getWidth(), originalBitmap.getHeight(), Config.ARGB_8888);

            // 创建画布
            Canvas canvas = new Canvas(roundedBitmap);

            // 创建画笔
            Paint paint = new Paint();
            paint.setAntiAlias(true);
            paint.setShader(shader);

            // 创建一个矩形，用于定义圆角区域
            RectF rectF = new RectF(0, 0, originalBitmap.getWidth(), originalBitmap.getHeight());

            // 在新的Bitmap上绘制圆角矩形
            canvas.drawRoundRect(rectF, radius, radius, paint);

            return roundedBitmap;

        } catch (Exception e) {

        }
        return null;
    }

    public static void addRoundedCornersToBitmap(Bitmap originalBitmap, float radius) {
        if (originalBitmap == null || radius <= 0) {
            return;
        }
        try {
            // 创建BitmapShader
            BitmapShader shader = new BitmapShader(originalBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);

            // 创建一个新的Bitmap
            Bitmap roundedBitmap = Bitmap.createBitmap(originalBitmap.getWidth(), originalBitmap.getHeight(), Config.ARGB_8888);

            // 创建画布
            Canvas canvas = new Canvas(roundedBitmap);

            // 创建画笔
            Paint paint = new Paint();
            paint.setAntiAlias(true);
            paint.setShader(shader);

            // 创建一个矩形，用于定义圆角区域
            RectF rectF = new RectF(0, 0, originalBitmap.getWidth(), originalBitmap.getHeight());

            // 在新的Bitmap上绘制圆角矩形
            canvas.drawRoundRect(rectF, radius, radius, paint);

            // 将绘制结果复制回原始Bitmap
            originalBitmap.eraseColor(0);
            Canvas originalCanvas = new Canvas(originalBitmap);
            originalCanvas.drawBitmap(roundedBitmap, 0, 0, null);
        } catch (Exception e) {

        }
    }


}
